﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FFmpegVideoClip.Lib;

namespace UtilityToolsCollect.ObjectsLibrary.ProcessManage
{
    public class ProcessShell : IDisposable
    {
        public static HashSet<ProcessShell> pss = new();
        public static readonly object PSSLockObject = new object();
        public readonly object DisposeLockObject = new object();
        private Process? m_Process;
        private CpuUtilizationPercentage? m_CPUUtilizationPercentage;
        public bool IsExistTask => m_Process != null;
        public bool IsRunning => IsExistTask && !m_Process!.HasExited ;
        public float ProcessImmediateValue
        {
            get
            {
                if (!IsRunning) return 0;
                if (m_CPUUtilizationPercentage == null)
                {
                    m_CPUUtilizationPercentage = new CpuUtilizationPercentage(m_Process!);
                    _ = m_CPUUtilizationPercentage.ImmediateValue;
                    Thread.Sleep(20);
                }
                return m_CPUUtilizationPercentage.ImmediateValue;
            }
        }
        public float ProcessWeightedAverageValue
        {
            get
            {
                if (!IsRunning) return 0;
                if (m_CPUUtilizationPercentage == null)
                {
                    m_CPUUtilizationPercentage = new CpuUtilizationPercentage(m_Process!);
                    _ = m_CPUUtilizationPercentage.WeightedAverageValue;
                    Thread.Sleep(20);
                }
                return m_CPUUtilizationPercentage.WeightedAverageValue;
            }
        }
        
        public ExecuteSimpleResult ExecuteSimple(string ProcessPathOrName, string arguments, Encoding? encoding = null)
        {
            if (m_Process != null) throw new ProcessExecuteException($"当前进程：[{m_Process.StartInfo.FileName} {m_Process.StartInfo.Arguments}] 并未结束，不能在当前实例中启动新的进程");
            m_Process = new Process();
            m_Process.StartInfo.FileName = ProcessPathOrName;
            m_Process.StartInfo.Arguments = arguments;
            m_Process.StartInfo.UseShellExecute = false;
            m_Process.StartInfo.CreateNoWindow = true;
            if (encoding != null)
            {
                m_Process.StartInfo.StandardOutputEncoding = encoding;
                m_Process.StartInfo.StandardErrorEncoding = encoding;
            }
            m_Process.StartInfo.RedirectStandardOutput = true;
            m_Process.StartInfo.RedirectStandardError = true;
            m_Process.Start();
            ExecuteSimpleResult Result = (m_Process.StandardOutput.ReadToEnd(), m_Process.StandardError.ReadToEnd());
            Kill();
            return Result;
        }

        public void ExecuteWaitForExit(string ProcessPathOrName, string? arguments, Action<string?>? OutputReceived, Action<string?>? ErrorReceived, Action<ProcessShell>? OnClossed = null, Encoding? encoding = null, Action<ProcessShell>? BeforeExecute = null)
        {
            if (m_Process!=null) throw new ProcessExecuteException($"当前进程：[{m_Process.StartInfo.FileName} {m_Process.StartInfo.Arguments}] 并未结束，不能在当前实例中，启动新的进程");
            LogManage.WriteLineNewInfo($"{ProcessPathOrName} {arguments}");
            BeforeExecute?.Invoke(this);
            m_Process = new Process();
            m_Process.StartInfo.FileName = ProcessPathOrName;
            m_Process.StartInfo.Arguments = arguments;
            m_Process.StartInfo.UseShellExecute = false;
            m_Process.StartInfo.CreateNoWindow = true;
            if (encoding != null)
            {
                m_Process.StartInfo.StandardOutputEncoding = encoding;
                m_Process.StartInfo.StandardErrorEncoding = encoding;
            }
            if (ErrorReceived != null)
            {
                m_Process.StartInfo.RedirectStandardError = true;
                m_Process.ErrorDataReceived += (o, e) => ErrorReceived?.Invoke(e.Data);
            }
            if (OutputReceived != null)
            {
                m_Process.StartInfo.RedirectStandardOutput = true;
                m_Process.OutputDataReceived += (o, e) => OutputReceived?.Invoke(e.Data);
            }

            lock (PSSLockObject) { pss.Add(this); }
            m_Process.Start();

            if (m_Process.StartInfo.RedirectStandardOutput)
                m_Process.BeginOutputReadLine();
            if (m_Process.StartInfo.RedirectStandardError)
                m_Process.BeginErrorReadLine();

            m_Process.WaitForExit();

            Kill();
 
            OnClossed?.Invoke(this);
        }
        public async Task ExecuteWaitForExitAsync(string ProcessPathOrName, string? arguments, Action<string?>? OutputReceived, Action<string?>? ErrorReceived, Action<ProcessShell>? OnClossed = null, Encoding? encoding = null, Action<ProcessShell>? BeforeExecute = null)
        {
            if (m_Process != null) throw new ProcessExecuteException($"当前进程：[{m_Process.StartInfo.FileName} {m_Process.StartInfo.Arguments}] 并未结束，不能在当前实例中，启动新的进程");
            LogManage.WriteLineNewInfo($"{ProcessPathOrName} {arguments}");
            BeforeExecute?.Invoke(this);
            m_Process = new Process();
            m_Process.StartInfo.FileName = ProcessPathOrName;
            m_Process.StartInfo.Arguments = arguments;
            m_Process.StartInfo.UseShellExecute = false;
            m_Process.StartInfo.CreateNoWindow = true;
            m_Process.EnableRaisingEvents = true;
            if (encoding != null)
            {
                m_Process.StartInfo.StandardOutputEncoding = encoding;
                m_Process.StartInfo.StandardErrorEncoding = encoding;
            }
            if (ErrorReceived != null)
            {
                m_Process.StartInfo.RedirectStandardError = true;
                m_Process.ErrorDataReceived += (o, e) => ErrorReceived?.Invoke(e.Data);
            }
            if (OutputReceived != null)
            {
                m_Process.StartInfo.RedirectStandardOutput = true;
                m_Process.OutputDataReceived += (o, e) => OutputReceived?.Invoke(e.Data);
            }

            lock (PSSLockObject) { pss.Add(this); }
            m_Process.Start();

            if (m_Process.StartInfo.RedirectStandardOutput)
                m_Process.BeginOutputReadLine();
            if (m_Process.StartInfo.RedirectStandardError)
                m_Process.BeginErrorReadLine();

            await m_Process.WaitForExitAsync();

            Kill();

            OnClossed?.Invoke(this);
        }
        public void ExecuteAsyn(string ProcessPathOrName, string? arguments, Action<string?>? OutputReceived, Action<string?>? ErrorReceived, Action<ProcessShell>? OnClossed, Encoding? encoding = null)
        {
            if (m_Process != null) throw new ProcessExecuteException($"当前进程：[{m_Process.StartInfo.FileName} {m_Process.StartInfo.Arguments}] 并未结束，不能在当前实例中，启动新的进程");
            m_Process = new Process();
            m_Process.StartInfo.FileName = ProcessPathOrName;
            m_Process.StartInfo.Arguments = arguments;
            m_Process.StartInfo.UseShellExecute = false;
            m_Process.StartInfo.CreateNoWindow = true;
            m_Process.EnableRaisingEvents = true;
            if (encoding != null)
            {
                m_Process.StartInfo.StandardOutputEncoding = encoding;
                m_Process.StartInfo.StandardErrorEncoding = encoding;
            }
            if (ErrorReceived != null)
            {
                m_Process.StartInfo.RedirectStandardError = true;
                m_Process.ErrorDataReceived += (o, e) => ErrorReceived?.Invoke(e.Data);
            }
            if (OutputReceived != null)
            {
                m_Process.StartInfo.RedirectStandardOutput = true;
                m_Process.OutputDataReceived += (o, e) => OutputReceived?.Invoke(e.Data);
            }

            m_Process.Exited += (o, e) => { Kill(); OnClossed?.Invoke(this); };
            
            lock (PSSLockObject) { pss.Add(this); }
            
            m_Process.Start();
            
            if (m_Process.StartInfo.RedirectStandardOutput)
                m_Process.BeginOutputReadLine();
            if (m_Process.StartInfo.RedirectStandardError)
                m_Process.BeginErrorReadLine();
        }

        public void ExecuteAsyn(ProcessTask processTask)
        {
            if (processTask.EnableDefaultSearch)
                processTask.ProcessPathOrName = DefaultSearch(processTask.ProcessPathOrName);
            ExecuteAsyn(processTask.ProcessPathOrName, processTask.Arguments, processTask.OutputReceived, processTask.ErrorReceived, processTask.OnClossed, processTask.Encoding);
        }

        public void Kill(bool entireProcessTree = true)
        {
            lock (DisposeLockObject)
            {
                if (m_Process != null)
                {
                    try
                    {
                        if (!m_Process.HasExited)
                            m_Process.Kill(entireProcessTree);
                    }
                    catch (Exception e) { LogManage.WriteExceptionInfo($"[进程释放异常：]{m_Process.StartInfo.FileName} {m_Process.StartInfo.Arguments}", e); }
                    m_Process.Close();
                    m_Process.Dispose();
                    m_Process = null;
                }
                if (m_CPUUtilizationPercentage != null)
                {
                    m_CPUUtilizationPercentage.Dispose();
                    m_CPUUtilizationPercentage = null;
                }
            }
            lock (PSSLockObject)
            {
                if (pss.Contains(this))
                    pss.Remove(this);
            }
        }

        #region Dispose
        private bool disposedValue;
        private void dispose()
        {
            if (!disposedValue)
            {
                Kill();
                disposedValue = true;
            }
        }
        ~ProcessShell() { dispose(); }

        public void Dispose() { dispose(); GC.SuppressFinalize(this); }
        #endregion

        public static ExecuteSimpleResult ProcessExecuteSimple(string ProcessPathOrName, string arguments
            , Encoding? encoding = null, bool EnableDefaultSearch = false)
        {
            if (EnableDefaultSearch)
                ProcessPathOrName = DefaultSearch(ProcessPathOrName);
            return new ProcessShell().ExecuteSimple(ProcessPathOrName, arguments, encoding);
        }

        public static void ProcessExecuteWaitForExit(string ProcessPathOrName, string arguments, Action<string?>? OutputReceived = null
            , Action<string?>? ErrorReceived = null, Action<ProcessShell>? OnClossed = null, Encoding? encoding = null, bool EnableDefaultSearch = false, Action<ProcessShell>? BeforeExecute = null)
        {
            if (EnableDefaultSearch)
                ProcessPathOrName = DefaultSearch(ProcessPathOrName);
            new ProcessShell().ExecuteWaitForExit(ProcessPathOrName, arguments, OutputReceived, ErrorReceived, OnClossed, encoding,  BeforeExecute);
        }
        public static async Task ProcessExecuteWaitForExitAsync(string ProcessPathOrName, string arguments, Action<string?>? OutputReceived = null
            , Action<string?>? ErrorReceived = null, Action<ProcessShell>? OnClossed = null, Encoding? encoding = null, bool EnableDefaultSearch = false, Action<ProcessShell>? BeforeExecute = null)
        {
            if (EnableDefaultSearch)
                ProcessPathOrName = DefaultSearch(ProcessPathOrName);
           await new ProcessShell().ExecuteWaitForExitAsync(ProcessPathOrName, arguments, OutputReceived, ErrorReceived, OnClossed, encoding,  BeforeExecute);
        }
        public static ProcessShell ProcessExecuteAsyn(string ProcessPathOrName, string arguments, Action<string?>? OutputReceived = null
            , Action<string?>? ErrorReceived = null, Action<ProcessShell>? OnClossed = null, Encoding? encoding = null,  bool EnableDefaultSearch = false)
        {
            if (EnableDefaultSearch)
                ProcessPathOrName = DefaultSearch(ProcessPathOrName);
            ProcessShell processEx = new ProcessShell();
            processEx.ExecuteAsyn(ProcessPathOrName, arguments, OutputReceived, ErrorReceived, OnClossed, encoding);
            return processEx;
        }


        public static bool SearchPath(string ProgramName, out string? FullPath)
        {
            string? tempPath, ExtensionName;
            if (string.IsNullOrEmpty(ProgramName)) throw new ArgumentNullException("没有指定程序的名称");
            ExtensionName = Path.GetExtension(ProgramName);
            if (string.IsNullOrEmpty(ExtensionName)) throw new ArgumentException("参数不是一个合法的文件名");
            ExtensionName = "*" + ExtensionName;

            //搜索启动目录
            tempPath = AppDomain.CurrentDomain.BaseDirectory;
            FullPath = Path.Combine(tempPath, ProgramName);
            if (File.Exists(FullPath)) return true;

            //搜索启动目录树
            FullPath = Directory.EnumerateFiles(tempPath, ExtensionName, SearchOption.AllDirectories)
                .FirstOrDefault(p => Path.GetFileName(p).Equals(ProgramName, StringComparison.OrdinalIgnoreCase));
            if (!string.IsNullOrEmpty(FullPath) && File.Exists(FullPath))
                return true;

            //搜索环境变量目录
            IEnumerable<string> EvnPaths = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User)!.Split(';')
                  .Concat(Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Process)!.Split(';'))
                  .Concat(Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine)!.Split(';'))
                  .Distinct().Where(p => !string.IsNullOrWhiteSpace(p) && Directory.Exists(p));
            FullPath = EvnPaths.FirstOrDefault(p => File.Exists(Path.Combine(p, ProgramName)));
            if (!string.IsNullOrEmpty(FullPath) && File.Exists(FullPath))
                return true;

            //搜索环境变量目录树
            CancellationTokenSource token = new CancellationTokenSource();
            FullPath = tempPath = null;
            try
            {
                FullPath = EvnPaths.AsParallel().WithCancellation(token.Token).WithMergeOptions(ParallelMergeOptions.NotBuffered)
                .FirstOrDefault(p =>
                {
                    string? resultFullName = Directory.EnumerateFiles(p, ExtensionName, SearchOption.AllDirectories)
                    .FirstOrDefault(file => Path.GetFileName(file).Equals(ProgramName, StringComparison.OrdinalIgnoreCase));
                    if (!string.IsNullOrEmpty(resultFullName))
                    {
                        tempPath = resultFullName;
                        token.Cancel();
                        return true;
                    }
                    return false;
                });
            }
            catch (OperationCanceledException Exc)
            {
                if (!string.IsNullOrEmpty(tempPath))
                { FullPath = tempPath; return false; }
                throw new DllNotFoundException("取消操作",Exc);
            }
            if (!string.IsNullOrEmpty(tempPath))
            { FullPath = tempPath; return true; }
            return false;
        }

        private static string DefaultSearch(string FileName)
        {
            if (SearchPath(FileName, out string? tempPath))
                return tempPath!;
            return FileName;
        }

        public static ProcessShell Create(Process process)
        {
            ProcessShell processShell = new ProcessShell { m_Process = process };
            lock (PSSLockObject) { pss.Add(processShell); }
            return processShell;
        }
    }

    [Serializable]
    public struct ExecuteSimpleResult
    {
        public string Result;
        public string? Error;
        public ExecuteSimpleResult(string result, string? error) { Result = result; Error = error; }
        public override bool Equals(object? obj) => Equals(obj, StringComparison.Ordinal);
        public bool Equals(object? obj, StringComparison comparisonType) => ToString().Equals(obj?.ToString(), comparisonType);
        public override int GetHashCode() => (Result + Error).GetHashCode();
        public override string ToString() => $"Result:{Result} Error:{Error}";
        public void Set(string result, string error) { Result = result; Error = error; }
        public static implicit operator ExecuteSimpleResult(string result) => new ExecuteSimpleResult(result, null);
        public static implicit operator string(ExecuteSimpleResult result) => result.Result;
        public static implicit operator ExecuteSimpleResult((string result, string error) value) => new ExecuteSimpleResult(value.result, value.error);
        //public static implicit operator ExecuteSimpleResult((string , string ) value) => new ExecuteSimpleResult(value.Item1, value.Item2);
    }

    [Serializable]
    public class ProcessExecuteException : Exception
    {
        public ProcessExecuteException() { }
        public ProcessExecuteException(string message) : base(message) { }
        public ProcessExecuteException(string message, Exception inner) : base(message, inner) { }
        [Obsolete]protected ProcessExecuteException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
    }

}
